# 基于TCP的聊天室程序：服务端

import selectors
import threading
import queue
import re
from socket import *
from time import ctime

BUFSIZE = 1024
HOST = input('HOST: ')
PORT = 21567
ADDR = (HOST, PORT)
tcpServSock = socket(AF_INET, SOCK_STREAM)
tcpServSock.bind(ADDR)
tcpServSock.listen(100)

user_list = []    # all registered users
user_buff = {}    # each user has a buffer
acti_user = []    # once a user logs in, he/she becomes active
acti_user_list = []

sel = selectors.DefaultSelector()
lock = threading.Lock()


def recv():
    global tcpServSock
    while True:
        lock.acquire(blocking=True)
        ret = sel.select(timeout=1)
        lock.release()
        for key, event in ret:    # some socket is readable
            if key.fileobj == tcpServSock:    # a new connection comes
                new_socket, new_addr = tcpServSock.accept()
                print('connected from %s [%s]' % (new_addr, ctime()))
                lock.acquire(blocking=True)
                sel.register(new_socket, selectors.EVENT_READ)
                lock.release()
            else:    # some one clicked Log In
                try:
                    ID = key.fileobj.recv(BUFSIZE).decode('utf-8')
                except:
                    print('%s disconnected' % (key.fileobj))
                    lock.acquire(blocking=True)
                    sel.unregister(key.fileobj)
                    lock.release()
                    continue
                if ID not in user_list:
                    user_list.append(ID)
                    acti_user_list.append(ID)
                    user_buff[ID] = queue.Queue(4096)
                    print('%s signed up [%s]' % (ID, ctime()))
                else:
                    if ID in acti_user_list:  # already logged in, deny
                        server_msg = 'FROME SERVER: already logged in'
                        key.fileobj.send(server_msg.encode('utf-8'))
                        continue
                    else:
                        acti_user_list.append(ID)
                    print('%s logged in [%s]' % (ID, ctime()))
                # create an instance for logged in user
                user = User(key.fileobj, ID)
                acti_user.append(user)
                user()
                lock.acquire(blocking=True)
                sel.unregister(key.fileobj)
                lock.release()


def forward():
    while True:
        for eachUser in acti_user:
            if eachUser.zombie == True:    # this user has logged out
                lock.acquire(blocking=True)
                # after user's loging out,
                sel.register(eachUser.socket, selectors.EVENT_READ)
                # recv_thread will take over the socket and listen to it
                lock.release()
                acti_user.remove(eachUser)
                del(eachUser)
                continue
            if eachUser.death == True:
                acti_user.remove(eachUser)
                del(eachUser)
                continue
            while user_buff[eachUser.ID].qsize():
                msg = user_buff[eachUser.ID].get()
                # some ckecking is desired, for the socket may not be writable
                eachUser.socket.send(msg.encode('utf-8'))


class User(object):
    def __init__(self, socket, ID):
        self.socket = socket
        self.ID = ID
        self.zombie = False
        self.death = False
        self.contact = None
        self.sel = selectors.DefaultSelector()
        self.sel.register(self.socket, selectors.EVENT_READ)

    def recv(self):
        while True:
            self.sel.select()
            try:
                msg = self.socket.recv(BUFSIZE).decode('utf-8')
            except:
                print('%s disconnected' % (self.socket))
                acti_user_list.remove(self.ID)
                self.death = True
                return None
            if re.match(r'^::[0-9]{6}', msg):
                # user wants to contact with some one
                contact = msg[2:]
                if contact not in user_list:
                    server_msg = 'FROME SERVER: no such user'
                    self.socket.send(server_msg.encode('utf-8'))
                else:
                    self.contact = msg[2:]
            elif msg == '::LOG OUT':
                # user wants to log out
                acti_user_list.remove(self.ID)
                print('%s logged out [%s]' % (self.ID, ctime()))
                self.zombie = True
                print(self.zombie)
                return None
            else:
                if self.contact != None:
                    msg = '[' + 'frome ' + self.ID + \
                        ' ' + ctime() + ']\n' + msg
                    user_buff[self.contact].put(msg)
                else:
                    server_msg = 'FROME SERVER: choose a contact first'
                    self.socket.send(server_msg.encode('utf-8'))

    def __call__(self):
        self.recv_thread = threading.Thread(target=self.recv)
        self.recv_thread.daemon = True
        self.recv_thread.start()


def main():
    fd = open(r'.\user.txt', 'r+')
    for eachUser in fd:
        user_list.append(eachUser[0:6])
        user_buff[eachUser[0:6]] = queue.Queue(4096)
    print(user_list)

    sel.register(tcpServSock, selectors.EVENT_READ)

    recv_thread = threading.Thread(target=recv)
    recv_thread.daemon = True
    recv_thread.start()

    forward_thread = threading.Thread(target=forward)
    forward_thread.daemon = True
    forward_thread.start()

    while True:
        pass    # infinate loop


if __name__ == '__main__':
    main()
